--[[
MIT License

Copyright (c) 2023 Anggara Yama Putra

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--]]
-- Globals
AnggaraNothing = AnggaraNothing or {}
do
end

-- class: HealthBar
do
  local root =  {}

  local strformat = string.format
  local strsub = string.sub
  local strfind = string.find
  local tabledefaults = AnggaraNothing.table_defaults
  local SetArg = AnggaraNothing.SetArg

  root.m_userdata_id = 'healthbar'
  root.m_ents = {}
  root.m_last_hp = nil
  root.m_last_goodhp = nil

  function root:_FindNext (start)
    local last = start or root.m_last_goodhp
    return function ()
      while last do
        if last:IsAlive() then
          local yield = last
          last = last.m_next
          return yield
        else
          last = last.m_next
          root.m_last_goodhp = last
        end
      end
      return nil
    end
  end

  function root:Create (owner)
    local instance = {
      owner = owner,
      ent = nil,
      m_health = 0,
      m_maxhealth = 0,
      m_monster = false,
      m_setarg = nil,
      m_next = nil,
    }

    -- SetArg setup
    local setarg = SetArg:Create( instance, {
      visible = false,
      type = 1289,
      skip = false,
    })
    -- SetArg.type
    function setarg.type:OnSet (oldvalue)
      local type = self.value
      if type == nil then
        type = 1289
      end
      local ent = root.m_ents[type]
      if not ent then
        ent = Game.Monster.Create( type, {x=-192,y=-192,z=95} )
        if not ent then
          error( 'cannot spawn monster', 3 )
        end
        ent:Stop( true )
        root.m_ents[type] = ent
        log( strformat(
          '%s::%s.OnSet::spawn %d type %d',
          instance, self, ent.index, ent.type
        ))
      end
      instance.ent = ent
      instance:Update()
      log( strformat(
        '%s::%s.OnSet::type %d index %d',
        instance, self, type, ent.index
      ))
    end
    -- SetArg.visible
    function setarg.visible:OnSet (oldvalue)
      instance:Update()
      log( strformat(
        '%s::%s.OnSet::%s',
        instance, self, self.value
      ))
    end
    instance.m_setarg = setarg

    local owner_id = nil
    local owner_str = tostring( owner )
    if owner and not strfind(owner_str, 'Game::VoxelEntity', 1, true) then
      -- Game.Entity owner
      instance.m_monster = true
      owner.user[self.m_userdata_id] = instance
      owner_id = owner.index
    else
      instance.m_health = 100
      instance.m_maxhealth = 100
      owner_id = strsub( owner_str, -8 )
    end

    local mt = root.m_metatable
    setmetatable(instance, tabledefaults(
      {
        __name = strformat( '%s(%s)', mt.__name, owner_id )
      }
    , mt))
    setarg:Init()

    -- Add to link
    if root.m_last_hp then
      root.m_last_hp.m_next = instance
    else
      root.m_last_goodhp = instance
    end
    root.m_last_hp = instance

    log( strformat(
      '%s::Create::%s',
      instance, instance.ent and instance.ent.type or nil
    ))
    return instance
  end

  function root:Set (args)
    return self.m_setarg:Set( args )
  end

  function root:Get ()
    return self.m_setarg:Get()
  end

  function root:Update (value)
    local owner = self.owner
    local is_monster = self.m_monster
    if value == nil then
      if is_monster then
        value = owner.health
      else
        value = self.m_health
      end
    end
    if value < 0 then
      value = 0
    end
    self.m_health = value

    local maxvalue = nil
    if is_monster then
      maxvalue = owner.maxhealth
    else
      maxvalue = self.m_maxhealth
    end
    self.m_maxhealth = maxvalue

    if self:Get().visible then
      local ent = self.ent
      ent.maxhealth = maxvalue
      ent.health = ((value > 1 and value or 1) // 1) | 0
    end

    if value <= 0 then
      -- If dead, then update the next healthbar
      for next_hp in self:_FindNext() do
        local args = next_hp:Get()
        if next_hp ~= self and args.visible and not args.skip then
          log( strformat(
            '%s::Update::next %s',
            self, next_hp
          ))
          next_hp:Update()
          return
        end
      end
    end
  end

  function root:GetUserdata (entity)
    entity = entity or self.owner
    return entity.user[self.m_userdata_id]
  end

  function root:IsAlive ()
    return self.m_health > 0
  end

  function root.OnTakeDamage (victim, attacker, damage, weapontype, hitbox)
    if victim and victim:IsMonster() and root.m_ents[victim.type] == victim then
      return 0
    end

    local userdata = root:GetUserdata( victim )
    if not userdata then
      return
    end

    local owner = userdata.owner
    if victim ~= owner then
      return
    end

    local ent = userdata.ent
    local new_hp = userdata.m_health
    if not ent or new_hp <= 0 then
      return
    end

    userdata:Update( new_hp - damage )
  end

  local metatable = {}
  metatable.__index = root
  metatable.__name = 'HealthBar'
  metatable.__type = 'HealthBar'

  root.m_metatable = metatable
  AnggaraNothing.HealthBar = root
end
